import * as PIXI from 'pixi.js';
import * as UI from '@pixi/ui'
import { DataBus } from '../utils/DataBus';
import { SgScript } from '../utils/SgScript';
import { NodeUtils } from '../utils/NodeUtils';
import { ComUtils } from '../utils/com';
import { $t } from '../../plugins/i18n';
import { SgToken } from '../svm/SgToken';
import { Queue } from '../utils/Queue';
import { AppComUtils } from '../../uitls/com';
import { NodesMenus } from './NodesMenus';
import { SgsEditor } from '../../uitls/sg-editor';
import { EditorOutputEvent } from '../../com/editor-struct';
import { MainNode } from '../nodes/MainNode';
export enum CompilerStatus {
    Running,
    Stop,
    Ready,
}

export class MainUi {
    public view: PIXI.Container
    private runBtn?: UI.Button
    private runTimer: any
    private logBox: HTMLDivElement
    private status: CompilerStatus = CompilerStatus.Stop
    private startTime = 0
    private nodesMenus: NodesMenus
    private lastRunClickTime = 0
    constructor() {
        this.view = new PIXI.Container()
        this.logBox = document.createElement('div')
        document.body.appendChild(this.logBox)
        this.logBox.className = 'log-box'
        this.view.zIndex = 1000
        DataBus.ui = this
        this.init()
        this.nodesMenus = new NodesMenus()
        this.nodesMenus.onClick = this.newNode.bind(this)
        this.view.addChild(this.nodesMenus.view)
    }
    private async init() {
        DataBus.nodesSelector
        await this.preload()
        if (import.meta.env.MODE !== 'vscode'){
            this.createTopActions()
            this.createBottomActions()
        }
        

        window.compiler?.onExecuteMessage(this.onExecuteMessage.bind(this))
        window.compiler?.onExecuteDone(this.onExecuteDone.bind(this))
        window.compiler?.resizeEditor(this.resizeEditor.bind(this))

        window.onresize = () => {
            this.resizeEditor()
        }

        DataBus.app.canvas.addEventListener('dragover', (e) => {
            e.preventDefault()
        })
        DataBus.app.canvas.addEventListener('drop', (e) => {
            e.preventDefault()
            this.onDrop(e)
        })

        if (import.meta.env.MODE === 'vscode') {
            const info: any = await SgsEditor.callEnvMethod(EditorOutputEvent.ENV_INFO, {})
            this.setContent(info.content)
        }

    }
    async onDrop(e: DragEvent) {
        if (!e.dataTransfer?.files[0]) {
            return
        }
        try {
            const data = await ComUtils.readFile(e.dataTransfer.files[0])
            DataBus.nodesBox.removeChildren()
            const script = await SgScript.decode(data)
            DataBus.nodes = script.nodes
            DataBus.nodesBox.x = script.data.stage.x
            DataBus.nodesBox.y = script.data.stage.y
            DataBus.nodesBox.scale.set(script.data.stage.scale)
            DataBus.nodes.forEach(node => {
                DataBus.nodesBox.addChild(node.view)
            })
        } catch (error) {
            this.queueLog(String(error))
        }
    }
    private onExecuteMessage(msg: string) {
        this.queueLog(msg)
    }
    public onExecuteDone() {
        this.status = CompilerStatus.Stop
        const time = Date.now() - this.startTime
        this.queueLog(`Done ${time}ms`)
    }
    private resizeEditor() {
        DataBus.app.resize()
        this.resetDebug()
    }

    private queueLog(msg: string) {
        if (import.meta.env.MODE === 'vscode'){
            return
        }
        const msgAr = msg.split(/%#%#/g)
        for (let i = 0; i < msgAr.length; i++) {
            if (!msgAr[i] || !msgAr[i].trim()) {
                continue
            }
            msgAr[i] = msgAr[i].trim()
            const date = ComUtils.formatTime(Date.now())
            const reg = /(editor:\s|svm:\s|Error:\s)/g
            const isErr = reg.test(msgAr[i])
            msgAr[i] = msgAr[i].replace(reg, '')
            let temp = msgAr[i].split(/\s+/)
            if (isErr && temp.length < 20) {
                // translate

                for (let i = 0; i < temp.length; i++) {
                    temp[i] = ($t(temp[i]) || temp[i])
                }
            }

            const log = `<span class="log-date">${date}</span> <pre class="log-msg">${ComUtils.encodeHtml(temp.join(' '))}</pre>`
            Queue.push({
                func: this.log.bind(this),
                args: [log]
            })
        }

    }
    private setLogVisible(visible: boolean) {
        this.logBox.style.display = visible ? 'block' : 'none'
    }
    private getLogVisible() {
        return this.logBox.style.display == 'block'
    }
    private log(msg: any) {
        if (this.getLogVisible() == false) {
            return
        }
        this.logBox.innerHTML = `${this.logBox.innerHTML}${msg}`
        this.logBox.scrollTop = this.logBox.scrollHeight
    }
    private async preload() {
        const path = AppComUtils.getBasePath()
        await PIXI.Assets.load(path + 'ui/create.svg')
        await PIXI.Assets.load(path + 'ui/open.svg')
        await PIXI.Assets.load(path + 'ui/save.svg')
        await PIXI.Assets.load(path + 'ui/running.svg')
        await PIXI.Assets.load(path + 'ui/run.svg')
        await PIXI.Assets.load(path + 'ui/add.svg')
        await PIXI.Assets.load(path + 'ui/export.svg')
        await PIXI.Assets.load(path + 'ui/teminal.svg')
    }
    private async createTopActions() {
        const path = AppComUtils.getBasePath()
        const list = new UI.List({
            type: 'horizontal',
            padding: 5,
            elementsMargin: 5
        })
        this.view.addChild(list)

        const bg0 = PIXI.Texture.from(path + 'ui/create.svg')
        const createBtn = new UI.Button(new PIXI.Sprite(bg0))
        createBtn.view.width = 30
        createBtn.view.height = 30
        createBtn.onPress.connect(this.newDocument.bind(this))

        const bg1 = PIXI.Texture.from(path + 'ui/open.svg')
        const loadBtn = new UI.Button(new PIXI.Sprite(bg1))
        loadBtn.view.width = 30
        loadBtn.view.height = 30
        loadBtn.onPress.connect(this.loadScript.bind(this))

        const bg2 = PIXI.Texture.from(path + 'ui/save.svg')
        const saveBtn = new UI.Button(new PIXI.Sprite(bg2))
        saveBtn.view.width = 30
        saveBtn.view.height = 30
        saveBtn.onPress.connect(this.saveScript.bind(this))

        const bg3 = PIXI.Texture.from(path + 'ui/export.svg')
        const exportBtn = new UI.Button(new PIXI.Sprite(bg3))
        exportBtn.view.width = 30
        exportBtn.view.height = 30
        exportBtn.onPress.connect(this.exportScript.bind(this))

        const newBtn = this.createNewBtn()

        this.runBtn = await this.createRunBtn()

        const logBtn = this.createLogBtn()

        list.addChild(createBtn.view, loadBtn.view, saveBtn.view, exportBtn.view, newBtn, this.runBtn.view)
        if (import.meta.env.MODE !== 'vscode') {
            list.addChild(logBtn.view)
        }

    }

    private createBottomActions() {


    }

    private resetDebug() {
        if (!this.getLogVisible()) {
            this.logBox.innerHTML = ''
            this.logBox.scrollTo({
                top: 0
            })
        }
    }
    private showLog() {
        this.setLogVisible(true)
    }
    private createLogBtn() {
        const path = AppComUtils.getBasePath()
        const bg = PIXI.Texture.from(path + 'ui/teminal.svg')
        const debugBtn = new UI.Button(new PIXI.Sprite(bg))
        debugBtn.view.width = 30
        debugBtn.view.height = 30
        debugBtn.onPress.connect(() => {
            this.setLogVisible(!this.getLogVisible())
            if (!this.getLogVisible()) {
                this.logBox.innerHTML = ''
                this.logBox.scrollTo({
                    top: 0
                })
            }
        })
        return debugBtn
    }

    private update() {
        const path = AppComUtils.getBasePath()
        if (this.status === CompilerStatus.Stop) {
            clearInterval(this.runTimer)
            if (!this.runBtn) {
                return
            }
            (this.runBtn.view as PIXI.Sprite).texture = PIXI.Texture.from(path + 'ui/run.svg')
        } else {
            if (!this.runBtn) {
                return
            }
            (this.runBtn.view as PIXI.Sprite).texture = PIXI.Texture.from(path + 'ui/running.svg')
        }
    }

    private async createRunBtn() {
        const path = AppComUtils.getBasePath()
        const bg1 = PIXI.Texture.from(path + 'ui/run.svg')
        const runBtn = new UI.Button(new PIXI.Sprite(bg1))
        runBtn.onPress.connect(this.runScript.bind(this))
        runBtn.view.width = 30
        runBtn.view.height = 30

        return runBtn
    }

    private createNewBtn() {
        const path = AppComUtils.getBasePath()
        const bg = PIXI.Texture.from(path + 'ui/add.svg')
        const newBtn = new PIXI.Container()
        const addBtn = new PIXI.Sprite(bg)
        addBtn.width = 30
        addBtn.height = 30
        addBtn.interactive = true
        addBtn.cursor = 'pointer'
        newBtn.addChild(addBtn)
        addBtn.onpointertap = () => {
            this.nodesMenus.show()
        }
        return newBtn
    }
    newDocument() {
        //base node
        DataBus.nodesBox.removeChildren()
        DataBus.nodes = []
        const main = new MainNode()
        main.center()
        DataBus.nodes.push(main)
        DataBus.nodesBox.addChild(main.view)
    }
    private saveScript() {
        const json = SgScript.encode({
            x: DataBus.nodesBox.x,
            y: DataBus.nodesBox.y,
            scale: DataBus.nodesBox.scale.x
        }, DataBus.nodes)
        SgsEditor.callEnvMethod(EditorOutputEvent.SAVE_FILE, json)
    }

    public exportScript() {
        const tokens = SgToken.create(DataBus.nodes)
        const json = JSON.stringify(tokens)
        SgsEditor.callEnvMethod(EditorOutputEvent.COMPILE_CODE, json)
    }

    async runScript() {
        const now = Date.now()
        if (now - this.lastRunClickTime < 800) {
            return
        }
        this.lastRunClickTime = now
        if (this.status === CompilerStatus.Running) {
            SgsEditor.callEnvMethod(EditorOutputEvent.STOP_VM)
            return
        }
        if (import.meta.env.MODE !== 'vscode') {
            this.showLog()
            this.status = CompilerStatus.Running
            this.runTimer = setInterval(this.update.bind(this), 100)
        }
        
        try {
            const tokens = SgToken.create(DataBus.allNodes)
            this.startTime = Date.now()
            SgsEditor.callEnvMethod(EditorOutputEvent.EXECUTE_CODE, tokens)
        } catch (error) {
            this.status = CompilerStatus.Stop
            this.queueLog(String(error))
        }
    }
    private async newNode(t: string) {
        try {
            const nodeType = t.substring(0, 1).toUpperCase() + t.substring(1)
            const nodeName = `${nodeType}Node`
            const nodes = DataBus.nodes.filter(node => node.titleContent.includes(nodeType))
            const ids = nodes.map(node => {
                const temp = node.titleContent.trim().split('_')
                return Number(temp[1])
            })
            const max = (ids.length > 0 ? Math.max(...ids) : 0) + 1
            const newName = `${nodeType}_${max}`
            const node = await NodeUtils.newNode(nodeType, newName)
            if (!node) {
                console.error(`node ${nodeName} not found`)
                return
            }
            node.center()
            DataBus.nodesBox.addChild(node.view)
            DataBus.nodes.push(node)

            DataBus.updateNode()
        } catch (error) {
            console.error(error)
        }
    }
    openNodeSelector() {
        this.nodesMenus.show()
    }
    private async loadScript() {
        try {
            const res: any = await SgsEditor.callEnvMethod(EditorOutputEvent.OPEN_FILE)
            if (!res.data) {
                return
            }
            this.setContent(res.data)
        } catch (_) {
            this.queueLog('code parse error')
        }
    }

    async setContent(content: string) {
        try {

            DataBus.nodesBox.removeChildren()
            const script = await SgScript.decode(content)
            DataBus.nodes = script.nodes
            DataBus.nodesBox.x = script.data.stage.x
            DataBus.nodesBox.y = script.data.stage.y
            DataBus.nodesBox.scale.set(script.data.stage.scale)
            DataBus.nodes.forEach(node => {
                DataBus.nodesBox.addChild(node.view)
            })
        } catch (_) {
            this.queueLog('code parse error')
        }
    }
}